#ifndef _FNT_PLUGIN_H_
#define _FNT_PLUGIN_H_

#include <vector>
#include "annot.h"
#include "graph.h"
#include "utils.h"
#include "config-fnt.h"

class QWidget;

enum class ParamType {
	INT,
	FLOAT,
	ENUM
};

class ParamValue {
	ParamType type;
	union {
		int asInt; // store enum here too
		double asFloat;
	};
	public:
	ParamValue(int i): type{ParamType::INT}, asInt{i} { }
	ParamValue(double d): type{ParamType::FLOAT}, asFloat{d} { }

	int getInt() const { if(type!=ParamType::INT) throwError("Not an integer."); return asInt; }
	double getFloat() const { if(type!=ParamType::FLOAT) throwError("Not a float."); return asFloat; }
	int getEnum() const { if(type!=ParamType::INT) throwError("Not an enum."); return asInt; }

	void setInt(int i) { type=ParamType::INT; asInt=i; }
	void setFloat(double i) { type=ParamType::FLOAT; asFloat=i; }
	void setEnum(int i) { type=ParamType::INT; asInt=i; }

	bool operator==(const ParamValue& v) {
		if(type!=v.type)
			return false;
		switch(type) {
			case ParamType::INT:
				return asInt==v.asInt;
			case ParamType::FLOAT:
				return asFloat==v.asFloat;
			case ParamType::ENUM:
				throwError("Unexpected param value type.");
		}
		return false;
	}
	bool operator!=(const ParamValue& v) { return !operator==(v); }
};
class ParamSpec {
	ParamType _type;
	const char* _name;
	const char* _desc;

	struct AsInt {
		int minimum;
		int maximum;
		int initial;
		const char* unit;
	};
	struct AsFloat {
		double minimum;
		double maximum;
		double initial;
		const char* unit;
	};
	struct AsEnum {
		std::vector<const char*> choices;
		char initial;
	};
	union {
		struct AsInt _asInt;
		struct AsFloat _asFloat;
		struct AsEnum _asEnum;
	};
	public:
	ParamSpec(const ParamSpec& p):
		_type{p._type}, _name{p._name}, _desc{p._desc}
	{
		switch(_type) {
			case ParamType::INT:
				new(&_asInt) AsInt(p._asInt);
				break;
			case ParamType::FLOAT:
				new(&_asFloat) AsFloat(p._asFloat);
				break;
			case ParamType::ENUM:
				new(&_asEnum) AsEnum(p._asEnum);
				break;
		}
	}

	ParamSpec(const char* n, const char* d, int min, int max, int v, const char* u):
		_type{ParamType::INT}, _name{n}, _desc{d},
		_asInt{min, max, v, u}
	{
	}
	ParamSpec(const char* n, const char* d, double min, double max, double v, const char* u):
		_type{ParamType::FLOAT}, _name{n}, _desc{d},
		_asFloat{min, max, v, u}
	{
	}
	ParamSpec(const char* n, const char* d, const std::initializer_list<const char*> c, char v):
		_type{ParamType::ENUM}, _name{n}, _desc{d},
		_asEnum{c, v}
	{
	}
	~ParamSpec() {
		switch(_type) {
			case ParamType::INT:
				_asInt.~AsInt();
				break;
			case ParamType::FLOAT:
				_asFloat.~AsFloat();
				break;
			case ParamType::ENUM:
				_asEnum.~AsEnum();
				break;
		}
	}
	const AsInt& asInt() const { if(_type!=ParamType::INT) throwError("Not an integer parameter."); return _asInt; }
	const AsFloat& asFloat() const { if(_type!=ParamType::FLOAT) throwError("Not a float parameter."); return _asFloat; }
	const AsEnum& asEnum() const { if(_type!=ParamType::ENUM) throwError("Not an enum parameter."); return _asEnum; }
	ParamType type() const { return _type; }
	const char* name() const { return _name; }
	const char* desc() const { return _desc; }
};

struct AlgorithmInfo {
	const char* name;
	int priority; // 0-100
	const char* desc;
	std::vector<ParamSpec> paramSpecs;
	AlgorithmInfo(const char* n, int p, const char* d, const std::initializer_list<ParamSpec>& pars): name{n}, priority{p}, desc{d}, paramSpecs{pars} { }
	virtual ~AlgorithmInfo() { }
};
template<typename Alg>
struct AlgInfo: public AlgorithmInfo {
	Alg* (*creater)();
	AlgInfo(const char* n, int p, const char* d, const std::initializer_list<ParamSpec>& pars, Alg*(*c)()): AlgorithmInfo{n, p, d, pars}, creater{c} { }
};

struct CubeRef {
	int64_t width, height, depth;
	int64_t widthAdj, heightAdj, depthAdj;
	double xres, yres, zres;

	double origin[3];
	double dir[9];
	double rdir[9];

	union {
		const char* data;
		const uint8_t* dataU8;
		const uint16_t* dataU16;
	};
	bool is16bit;

	CubeRef(): width{0}, height{0}, depth{0}, widthAdj{0}, heightAdj{0}, depthAdj{0}, xres{0.0}, yres{0.0}, zres{0.0}, origin{0,}, dir{0,}, rdir{0,}, data{nullptr}, is16bit{false} { }

	bool valid() const { return data; }

	double voxel(int64_t i) const { return is16bit?dataU16[i]/65535.0:dataU8[i]/255.0; }
	uint32_t voxel_uint(int64_t i) const { return is16bit?dataU16[i]:dataU8[i]; }
	void index2xyzf(int64_t i, double* px, double* py, double* pz) const {
		double x=i%widthAdj+.5;
		i/=widthAdj;
		double y=i%heightAdj+.5;
		i/=heightAdj;
		double z=i+.5;
		*px=x*dir[0]+y*dir[3]+z*dir[6]+origin[0];
		*py=x*dir[1]+y*dir[4]+z*dir[7]+origin[1];
		*pz=x*dir[2]+y*dir[5]+z*dir[8]+origin[2];
	}
	int64_t xyzf2index(double x, double y, double z) const {
		x-=origin[0];
		y-=origin[1];
		z-=origin[2];
		int32_t xi=x*rdir[0]+y*rdir[3]+z*rdir[6];
		if(xi<0||xi>=width) return -1;
		int32_t yi=x*rdir[1]+y*rdir[4]+z*rdir[7];
		if(yi<0||yi>=height) return -1;
		int32_t zi=x*rdir[2]+y*rdir[5]+z*rdir[8];
		if(zi<0||zi>=depth) return -1;
		return xi+widthAdj*(yi+heightAdj*zi);
	}
	void xyzf2xyzi(double x, double y, double z, int32_t* px, int32_t* py, int32_t* pz) const {
		x-=origin[0];
		y-=origin[1];
		z-=origin[2];
		*px=x*rdir[0]+y*rdir[3]+z*rdir[6];
		*py=x*rdir[1]+y*rdir[4]+z*rdir[7];
		*pz=x*rdir[2]+y*rdir[5]+z*rdir[8];
	}
	void index2xyzi(int64_t i, int32_t* px, int32_t* py, int32_t* pz) const {
		*px=i%widthAdj;
		i/=widthAdj;
		*py=i%heightAdj;
		i/=heightAdj;
		*pz=i;
	}
	int64_t xyzi2index(int32_t xi, int32_t yi, int32_t zi) const {
		return xi+widthAdj*(yi+heightAdj*zi);
	}
};

class Algorithm {
	bool (*_callback_func)(Algorithm*, void*);
	void* _callback_data;
	const AlgorithmInfo* _info;

	CubeRef _cubes[3];
	std::pair<double, double> _xfuncs[3];

	protected:

	Algorithm(): _callback_func{nullptr}, _callback_data{nullptr}, _info{}, _cubes{}, _xfuncs{} { }

	bool sync(int skip) {
		static int prev=0;
		prev++;
		if(prev%skip!=0)
			return true;
		if(!_callback_func)
			return true;
		return _callback_func(this, _callback_data);
	}

	virtual void positionChanged() { }
	virtual void cubeUpdated(int ch) { }
	virtual void xfuncUpdated(int ch) { }

	template <int ch>
	int64_t width() const { return _cubes[ch].width; }
	template <int ch>
	int64_t height() const { return _cubes[ch].height; }
	template <int ch>
	int64_t depth() const { return _cubes[ch].depth; }

	template <int ch>
	double xres() const { return _cubes[ch].xres; }
	template <int ch>
	double yres() const { return _cubes[ch].yres; }
	template <int ch>
	double zres() const { return _cubes[ch].zres; }

	template <int ch>
	double cube(int64_t i) const { return _cubes[ch].voxel(i); }
	template <int ch>
	double xfunc(double v) const {
		auto& p=_xfuncs[ch];
		if(v<p.first)
			return 0.0;
		if(v>p.second)
			return 1.0;
		auto d=p.second-p.first;
		if(d==0)
			return .5;
		return (v-p.first)/(p.second-p.first);
	}

	template <int ch>
	void index2xyzf(int64_t i, double* px, double* py, double* pz) const {
		return _cubes[ch].index2xyzf(i, px, py, pz);
	}
	template <int ch>
	int64_t xyzf2index(double x, double y, double z) const {
		return _cubes[ch].xyzf2index(x, y, z);
	}
	template <int ch>
	void xyzf2xyzi(double x, double y, double z, int32_t* px, int32_t* py, int32_t* pz) const {
		return _cubes[ch].xyzf2xyzi(x, y, z, px, py, pz);
	}
	template <int ch>
	void index2xyzi(int64_t i, int32_t* px, int32_t* py, int32_t* pz) const {
		return _cubes[ch].index2xyzi(i, px, py, pz);
	}
	template <int ch>
	int64_t xyzi2index(int32_t xi, int32_t yi, int32_t zi) const {
		return _cubes[ch].xyzi2index(xi, yi, zi);
	}

	public:
	const AlgorithmInfo* info() const;
	virtual ~Algorithm() { }

	virtual std::vector<int32_t> getUpdate() const { return {}; }

	void updateCube(int ch, const CubeRef& c, const std::pair<double, double>& xfunc);
	void updateXfunc(int ch, const std::pair<double, double>& xfunc);
	void updatePosition();

	void setCallback(bool (*cbf)(Algorithm*,void*), void* cbd);
	void setInfo(const AlgorithmInfo* info);

	virtual void setParams(const std::vector<ParamValue>& params) =0;
	virtual std::vector<ParamValue> getParams() const =0;
};

class AlgFind : public Algorithm {
	protected:
		AlgFind(): Algorithm{} { }
	public:
		~AlgFind() override { }
		virtual void find(const Position& p, Graph gr, Path* pth) =0;
};

class AlgConn : public Algorithm {
	protected:
		AlgConn(): Algorithm{} { }
	public:
		~AlgConn() override { }
		virtual void connect(const Position& p0, const Position& p1, Graph gr, Path* pth) =0;
};

class AlgRefine : public Algorithm {
	protected:
		AlgRefine(): Algorithm{} { }
	public:
		~AlgRefine() override { }
		virtual void refine(Graph gr, Path* pth) =0;
};

class GraphReader {
	CubeRef _cubes[3];
	std::pair<double, double> _xfuncs[3];
	CubeRef _cube_annot;
	const AnnotItem* _annots_ptr;
	uint32_t _annots_num;

	protected:
	GraphReader() { }

	template <int ch>
	int64_t width() const { return _cubes[ch].width; }
	template <int ch>
	int64_t height() const { return _cubes[ch].height; }
	template <int ch>
	int64_t depth() const { return _cubes[ch].depth; }

	int64_t width_annot() const { return _cube_annot.width; }
	int64_t height_annot() const { return _cube_annot.height; }
	int64_t depth_annot() const { return _cube_annot.depth; }

	template <int ch>
	double xres() const { return _cubes[ch].xres; }
	template <int ch>
	double yres() const { return _cubes[ch].yres; }
	template <int ch>
	double zres() const { return _cubes[ch].zres; }

	double xres_annot() const { return _cube_annot.xres; }
	double yres_annot() const { return _cube_annot.yres; }
	double zres_annot() const { return _cube_annot.zres; }

	template <int ch>
	double cube(int64_t i) const { return _cubes[ch].voxel(i); }

	uint32_t cube_annot(int64_t i) const { return _cube_annot.voxel_uint(i); }

	template <int ch>
	double xfunc(double v) const {
		auto& p=_xfuncs[ch];
		if(v<p.first)
			return 0.0;
		if(v>p.second)
			return 1.0;
		auto d=p.second-p.first;
		if(d==0)
			return .5;
		return (v-p.first)/(p.second-p.first);
	}

	const AnnotItem* annot(uint32_t id) const {
		if(id>0 && id<=_annots_num)
			return &_annots_ptr[id-1];
		return nullptr;
	}

	template <int ch>
	void index2xyzf(int64_t i, double* px, double* py, double* pz) const {
		return _cubes[ch].index2xyzf(i, px, py, pz);
	}
	template <int ch>
	int64_t xyzf2index(double x, double y, double z) const {
		return _cubes[ch].xyzf2index(x, y, z);
	}
	template <int ch>
	void xyzf2xyzi(double x, double y, double z, int32_t* px, int32_t* py, int32_t* pz) const {
		return _cubes[ch].xyzf2xyzi(x, y, z, px, py, pz);
	}
	template <int ch>
	void index2xyzi(int64_t i, int32_t* px, int32_t* py, int32_t* pz) const {
		return _cubes[ch].index2xyzi(i, px, py, pz);
	}
	template <int ch>
	int64_t xyzi2index(int32_t xi, int32_t yi, int32_t zi) const {
		return _cubes[ch].xyzi2index(xi, yi, zi);
	}

	void index2xyzf_annot(int64_t i, double* px, double* py, double* pz) const {
		return _cube_annot.index2xyzf(i, px, py, pz);
	}
	int64_t xyzf2index_annot(double x, double y, double z) const {
		return _cube_annot.xyzf2index(x, y, z);
	}
	void xyzf2xyzi_annot(double x, double y, double z, int32_t* px, int32_t* py, int32_t* pz) const {
		return _cube_annot.xyzf2xyzi(x, y, z, px, py, pz);
	}
	void index2xyzi_annot(int64_t i, int32_t* px, int32_t* py, int32_t* pz) const {
		return _cube_annot.index2xyzi(i, px, py, pz);
	}
	int64_t xyzi2index_annot(int32_t xi, int32_t yi, int32_t zi) const {
		return _cube_annot.xyzi2index(xi, yi, zi);
	}

	public:
	virtual ~GraphReader() { }

	void setupCube(int ch, const CubeRef& c, const std::pair<double, double>& x);
	void setupCubeAnnot(const CubeRef& c, const std::vector<AnnotItem>* a);

	virtual void read(Graph gr, QWidget* par) =0;
};

struct GraphReaderInfo {
	const char* name;
	int priority; // 0-100
	const char* desc;
	GraphReader* (*creater)();
};

struct Plugin {
	const char* fnt_version;
	const char* name;
	const char* desc;
	void (*const load_func)(std::vector<AlgorithmInfo*>& algs, std::vector<GraphReaderInfo*>& readers);
	void (*const unload_func)();
};
#if defined(_MSC_VER) || defined(__CYGWIN__) || defined(__MINGW32__) || defined( __BCPLUSPLUS__)  || defined( __MWERKS__)
#  if defined( BAP_LIBRARY_STATIC )
#    define PLUGIN_EXPORT
#  else
#    define PLUGIN_EXPORT   __declspec(dllexport)

#  endif

#else
#define PLUGIN_EXPORT
#endif
#define FNT_PLUGIN(name, load, unload, desc) Plugin fnt_plugin{PACKAGE_VERSION, name, desc, load, unload}

#endif
